package com.leadiv.ann.matrix;

/**
 *
 * @author leadiv
 */
public class MatrixMath {
    /**
     * Adds two matrices together. Both matrices must be the same size.
     * @param a Matrix to add
     * @param b Matrix to add
     * @return A new matrix the same size as Matrix a and Matrix b
     */
    public static Matrix add(final Matrix a, final Matrix b) { 
        final double a2d_result[][] = new double[a.getRows()][a.getCols()];
        int i_r, i_c, i_len, i_jlen;
        for(i_r=0, i_len=a.getRows(); i_r<i_len; i_r++) {
            for(i_c=0, i_jlen=a.getCols(); i_c<i_jlen; i_c++) {
                a2d_result[i_r][i_c] = a.get(i_r, i_c) + b.get(i_r, i_c);
            }
        }

        return new Matrix(a2d_result);
    }

    /**
     * Divides every cell by double b value.
     * @param a Matrix to apply the divisions on
     * @param b The value to divide by. This can not be 0.
     * @return A new matrix with the results of the division
     */
    public static Matrix divide(final Matrix a, final double b) {
        final double a2d_result[][] = new double[a.getRows()][a.getCols()];
        int i_r, i_c, i_len, i_jlen;
        for(i_r=0, i_len=a.getRows(); i_r<i_len; i_r++) {
            for(i_c=0, i_jlen=a.getCols(); i_c<i_jlen; i_c++) {
                a2d_result[i_r][i_c] = a.get(i_r, i_c) / b;
            }
        }

        return new Matrix(a2d_result);
    }

    /**
     * Calculates the dot product of the two vector matrices. Vector matrix a
     * vector matrix b must be the same size.
     * @param a A vector matrix
     * @param b A vector matrix
     * @return A double representing the dotProduct
     */
    public static double dotProduct(final Matrix a, final Matrix b) {
        final double[] aArray = a.toPackedArray();
        final double[] bArray = b.toPackedArray();
        final int len = aArray.length;
        double result = 0;
        int i;

        for(i=0; i<len; i++) {
            result += aArray[i] * bArray[i];
        }

        return result;
    }

    /**
     * Creates a square (rows = columns) matrix of all 0s except for a diagnal
     * set of 1s running from the northwest to the southeast corners.
     * @param size The size of the matrix to create
     * @return An identiy matrix.
     */
    public static Matrix identity(final int size) {
        final Matrix result = new Matrix(size, size);
        int i, len;

        for(i=0, len=size; i<len; i++) {
            result.set(i, i, 1);
        }

        return result;
    }

    /**
     * Multiplies every cell by double b value.
     * @param a Matrix to apply the multiplications on
     * @param b The value to divide by.
     * @return A new matrix with the results of the division
     */
    public static Matrix multiply(final Matrix a, final double b) {
        final double a2d_result[][] = new double[a.getRows()][a.getCols()];
        int i_r, i_c, i_len, i_jlen;
        for(i_r=0, i_len=a.getRows(); i_r<i_len; i_r++) {
            for(i_c=0, i_jlen=a.getCols(); i_c<i_jlen; i_c++) {
                a2d_result[i_r][i_c] = a.get(i_r, i_c) * b;
            }
        }

        return new Matrix(a2d_result);
    }

    /**
     * Multiplies one matrix by another and produces a third matrix. Matrices a
     * and b must be compatible. Column size of matrix a must equal the row size
     * of matrix b. The resulting matrix is of Rows A by Columns B. Example, if
     * A is a matrix of size 3x2 and B is a matrix of size of 2x6. Then the
     * resulting matrix would be 3x6.
     * @param a Matrix to multiply
     * @param b Matrix to multiply by
     * @return A matrix of size Arow by Bcolumn.
     */
    public static Matrix multiply(final Matrix a, final Matrix b) {
        double[][] data = new double[a.getRows()][b.getCols()];
        int r,c,rlen,clen;

        for(r=0, rlen=a.getRows(); r<rlen; r++) {
            for(c=0, clen=b.getCols(); c<clen; c++) {
                data[r][c] = MatrixMath.dotProduct(a.getRow(r), b.getCol(c));
            }
        }

        return new Matrix(data);
    }

    /**
     * Subtracts two matrices. Both matrices must be the same size.
     * @param a Matrix to subtract from
     * @param b Matrix to subtract by
     * @return A new matrix the same size as Matrix a and Matrix b
     */
    public static Matrix subtract(final Matrix a, final Matrix b) {
        final double a2d_result[][] = new double[a.getRows()][a.getCols()];
        int i_r, i_c, i_len, i_jlen;
        for(i_r=0, i_len=a.getRows(); i_r<i_len; i_r++) {
            for(i_c=0, i_jlen=a.getCols(); i_c<i_jlen; i_c++) {
                a2d_result[i_r][i_c] = a.get(i_r, i_c) - b.get(i_r, i_c);
            }
        }

        return new Matrix(a2d_result);
    }

    /**
     * Transposes the rows and columns in a new matrix. So if input is a matrix
     * of size 3x2 then the new transposed matrix is of size 2x3. All cells are
     * transposed as well. So the value at cell (r,c) in the input will be placed
     * at (c, r) in the new matrix.
     * @param input Matrix to transpose
     * @return A new matrix that is the transposed version of the input matrix.
     */
    public static Matrix transpose(final Matrix input) { 
        final double inverseMatrix[][] = new double[input.getCols()][input.getRows()];
        int r, c, rlen, clen;
        
        for(r=0, rlen=input.getRows(); r<rlen; r++) {
            for(c=0, clen=input.getCols(); c<clen; c++) {
                inverseMatrix[c][r] = input.get(r, c);
            }
        }
        
        return new Matrix(inverseMatrix);
    }

    /**
     * Calculates the squared length of a vector.
     * @param input A matrix
     * @return the vector length of the matrix
     */
    public static double vectorLength(final Matrix input) {
        double[] v = input.toPackedArray();
        double result = 0;
        int i, len;

        for(i=0, len=v.length; i<len; i++) {
            result += Math.pow(v[i], 2);
        }

        return Math.sqrt(result);
    }
}
